<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="cGM61" id="cGM61"><span data-lake-id="ua9a18398" id="ua9a18398">典型回答</span></h1>
  <p data-lake-id="u8d95a45b" id="u8d95a45b"><span data-lake-id="u109c708c" id="u109c708c">在关于JMM的介绍中，我们知道，JMM 是一种规范，它提供了一些列的机制来保证跨线程的内存可见性、有序性和原子性。</span></p>
  <p data-lake-id="u88ba8f7f" id="u88ba8f7f"><span data-lake-id="u48735782" id="u48735782">​</span><br></p>
  <p data-lake-id="u577e8710" id="u577e8710"><span data-lake-id="u9e675bcd" id="u9e675bcd">我们之前介绍过很多保证可见性的关键字，如volatile和synchronized等，其实，volatile和synchronized为啥可以保证可见性，也正是因为他们遵守了一个重要的happens-before原则（后文会介绍，Monitor Lock 和Volatile Variable 是happens - before中重要的两个原则）。</span></p>
  <p data-lake-id="udf5c40e4" id="udf5c40e4"><span data-lake-id="u547df36d" id="u547df36d">​</span><br></p>
  <p data-lake-id="ue414301b" id="ue414301b"><span data-lake-id="u508baf32" id="u508baf32">我们之前还介绍过一个原则，叫做as-if-serial，他意思指：不管怎么重排序，单线程程序的执行结果都不能被改变。编译器和处理器无论如何优化，都必须遵守as-if-serial语义。</span></p>
  <p data-lake-id="ua444bf6f" id="ua444bf6f"><span data-lake-id="u2adf0514" id="u2adf0514">​</span><br></p>
  <p data-lake-id="u14afd61e" id="u14afd61e"><span data-lake-id="u71281e80" id="u71281e80">​</span><br></p>
  <p data-lake-id="u748ea100" id="u748ea100"><span data-lake-id="u02ea83de" id="u02ea83de">这个as-if-serial语义是针对单线程的，但是如果在多线程情况下呢？有没有什么原则可以保证有序性呢？这就需要我们的happens-before原则了。</span></p>
  <p data-lake-id="u6a508966" id="u6a508966"><br></p>
  <p data-lake-id="ue7e95635" id="ue7e95635"><span data-lake-id="u7a2ba78e" id="u7a2ba78e">happens-before原则是一种用于描述多线程程序中操作执行顺序的规则。它是Java内存模型（Java Memory Model，JMM）的一部分：</span><strong><span data-lake-id="u3a6a91c5" id="u3a6a91c5">如果一个操作 A “happen-before” 另一个操作 B，那么 A 的结果对 B 是可见的。这个概念是理解线程间内存可见性的关键。</span></strong></p>
  <p data-lake-id="u49fa015d" id="u49fa015d"><strong><span data-lake-id="uc9c5e3a2" id="uc9c5e3a2">​</span></strong><br></p>
  <p data-lake-id="u8d8e17e5" id="u8d8e17e5"><strong><span data-lake-id="u444740bb" id="u444740bb">举一个例子，如以下代码：</span></strong></p>
  <p data-lake-id="u36116843" id="u36116843"><strong><span data-lake-id="u1a0b036b" id="u1a0b036b">​</span></strong><br></p>
  <pre lang="java"><code>

public class ThreadStartExample {
    private int startValue = 10;

    public void startNewThread() {
        startValue +=1；
        new Thread(() -&gt; {
            int localValue = startValue;  
        }).start();
    }
}
</code></pre>
  <p data-lake-id="uce986998" id="uce986998"><span data-lake-id="ua3a6dae4" id="ua3a6dae4">​</span><br></p>
  <p data-lake-id="u069fb027" id="u069fb027"><span data-lake-id="u03c627cd" id="u03c627cd">有两个线程，一个写startValue，一个读startValue，但是我们并没有用synchronized加锁，也没有用volatile修饰，那么，JVM是如何保证，在主线程中修改startValue的操作在子线程中是可见的呢？</span></p>
  <p data-lake-id="ude465aed" id="ude465aed"><span data-lake-id="ubd7f76f6" id="ubd7f76f6">​</span><br></p>
  <p data-lake-id="ue4f40550" id="ue4f40550"><span data-lake-id="u1bb64c37" id="u1bb64c37">这其实就是happens-before原则发挥的作用了。但是，happens-before原则也不是没有任何限制，任何场景都能happens-before的，还是有一些规则要求的。我们接下来介绍下每个规则以及附上一些代码演示</span></p>
  <p data-lake-id="ud685c6c2" id="ud685c6c2"><span data-lake-id="u23f22fbb" id="u23f22fbb">​</span><br></p>
  <h3 data-lake-id="p6AP2" id="p6AP2"><span data-lake-id="u31bb3957" id="u31bb3957">happends-before规则</span></h3>
  <p data-lake-id="u475bb7f3" id="u475bb7f3"><br></p>
  <p data-lake-id="uc1064332" id="uc1064332"><span data-lake-id="u798617e6" id="u798617e6">以下十几个happends-before原则的适用场景（节选自《深入理解Java虚拟机》，并做了一些描述上的修改，增加了代码示例，方便大家理解）</span></p>
  <p data-lake-id="uc4bbccfa" id="uc4bbccfa"><br></p>
  <ul list="u8ea91861">
   <li fid="u0c3da757" data-lake-id="u454f5705" id="u454f5705"><span data-lake-id="u6a8f7674" id="u6a8f7674">程序次序规则（Program Order Rule）：在单个线程内，按照程序代码的顺序，前一个操作 happens-before 后一个操作。</span></li>
  </ul>
  <p data-lake-id="uca214e14" id="uca214e14"><span data-lake-id="u08f59c78" id="u08f59c78">​</span><br></p>
  <pre lang="java"><code>
public class ProgramOrderExample {
    private int a = 0;
    private int b = 0;

    public void method() {
        a = 1;  // 操作1
        b = 2;  // 操作2，
        // 操作1 `happens-before` 操作2
    }
}

</code></pre>
  <p data-lake-id="uc58a146d" id="uc58a146d"><span data-lake-id="ud666280f" id="ud666280f">​</span><br></p>
  <ul list="u8f7b4ede">
   <li fid="u802e68a4" data-lake-id="u509eb628" id="u509eb628"><span data-lake-id="u979ddf27" id="u979ddf27">管程锁定规则（Monitor Lock Rule）：对一个锁的解锁 happens-before 随后对这个锁的加锁。即在 synchronized 代码块或方法中，释放锁之前的所有操作对于下一个获取这个锁的线程是可见的。</span></li>
  </ul>
  <p data-lake-id="uc8ef9d71" id="uc8ef9d71"><span data-lake-id="u6d786d96" id="u6d786d96">​</span><br></p>
  <pre lang="java"><code>
public class MonitorLockExample {
    private int value = 0;
    private final Object lock = new Object();

    public void increment() {
        synchronized(lock) {
            value++;  // 操作在锁内
        }
    }

    public int getValue() {
        synchronized(lock) {
            return value;  // 此操作 `happens-before` increment() 中的操作
        }
    }
}
</code></pre>
  <p data-lake-id="u6bc03aee" id="u6bc03aee"><br></p>
  <p data-lake-id="uccb4fd70" id="uccb4fd70"><span data-lake-id="ubc8bddbc" id="ubc8bddbc">increment 方法中对 value 的修改，在 getValue 方法获取锁之后是可见的。</span></p>
  <p data-lake-id="ub7962119" id="ub7962119"><span data-lake-id="u3bc54d1f" id="u3bc54d1f" class="lake-fontsize-12" style="color: rgb(15, 15, 15)">​</span><br></p>
  <ul list="ub4f25347">
   <li fid="ucdbe1391" data-lake-id="u43623259" id="u43623259"><span data-lake-id="u270cb355" id="u270cb355">volatile 变量规则（Volatile Variable Rule）：对一个 volatile 字段的写操作 happens-before 任意后续对这个字段的读操作。即确保 volatile 变量的写操作对其他线程立即可见。</span></li>
  </ul>
  <p data-lake-id="u0d6e8980" id="u0d6e8980"><span data-lake-id="uc09e6e3a" id="uc09e6e3a">​</span><br></p>
  <pre lang="java"><code>
public class VolatileExample {
    private volatile boolean flag = false;

    public void writeFlag() {
        flag = true;  // volatile 写操作
    }

    public boolean checkFlag() {
        return flag;  // 这里读取到的 flag 值 `happens-before` 写操作
    }
}
</code></pre>
  <p data-lake-id="u447b270b" id="u447b270b"><br></p>
  <p data-lake-id="uea871588" id="uea871588"><span data-lake-id="ub9d979eb" id="ub9d979eb">当一个线程调用 writeFlag()，另一个线程随后调用 checkFlag() 将看到 flag 为 true。</span></p>
  <p data-lake-id="u22e9dcbe" id="u22e9dcbe"><span data-lake-id="u8ae757eb" id="u8ae757eb">​</span><br></p>
  <ul list="ud710bed6">
   <li fid="ubaa72399" data-lake-id="udc605ff4" id="udc605ff4"><span data-lake-id="ua8a224e4" id="ua8a224e4">线程启动规则（Thread Start Rule）：对线程的 start() 方法的调用 happens-before 该线程的每个动作。确保线程启动时，主线程中对共享变量的写操作对于新线程是可见的。</span></li>
  </ul>
  <p data-lake-id="ua4f6965a" id="ua4f6965a"><span data-lake-id="uaab4a2d7" id="uaab4a2d7">​</span><br></p>
  <pre lang="java"><code>
public class ThreadStartExample {
    private int startValue = 10;

    public void startNewThread() {
        startValue +=1；
        new Thread(() -&gt; {
            int localValue = startValue;  // startValue 的值 `happens-before` 这里的读操作
            // 处理 localValue
        }).start();
    }
}

</code></pre>
  <p data-lake-id="ua240b0a4" id="ua240b0a4"><br></p>
  <p data-lake-id="uc5798f4c" id="uc5798f4c"><span data-lake-id="uc317280d" id="uc317280d">线程启动时，将看到 startValue 的值为 11。</span></p>
  <p data-lake-id="u96a7a387" id="u96a7a387"><span data-lake-id="u66458a6d" id="u66458a6d">​</span><br></p>
  <ul list="u542440a9">
   <li fid="u135b9573" data-lake-id="u8e9af2fc" id="u8e9af2fc"><span data-lake-id="u8e77c673" id="u8e77c673">线程终止规则（Thread Termination Rule）：一个线程的所有操作 happens-before 对这个线程的 join() 方法的成功返回。确保线程终止时，该线程中的所有操作对于调用 join() 方法的线程是可见的。</span></li>
  </ul>
  <p data-lake-id="u210239d8" id="u210239d8"><span data-lake-id="u22a45a5e" id="u22a45a5e">​</span><br></p>
  <pre lang="java"><code>
public class ThreadJoinExample {
    private int counter = 0;

    public void incrementInThread() throws InterruptedException {
        Thread thread = new Thread(() -&gt; {
            counter++;
        });
        thread.start();
        thread.join();  // `happens-before` counter 的读操作
        int value = counter;  // 这里能够看到线程中对 counter 的修改
    }
}

</code></pre>
  <p data-lake-id="u56f98889" id="u56f98889"><span data-lake-id="udabd48bf" id="udabd48bf">​</span><br></p>
  <p data-lake-id="u22888749" id="u22888749"><span data-lake-id="u064dda87" id="u064dda87">主线程中可以看到子线程对counter的修改<br><br></span></p>
  <ul list="u8e230744">
   <li fid="u008138e1" data-lake-id="u2fa3bfff" id="u2fa3bfff"><span data-lake-id="u1afb7d44" id="u1afb7d44">线程中断规则（Thread Interruption Rule）： 对线程的 interrupt() 方法的调用 happens-before 被中断线程检测到中断事件的发生。即线程的中断操作在被该线程检测到之前已经发生。</span></li>
  </ul>
  <p data-lake-id="ue191299e" id="ue191299e"><span data-lake-id="udeaaf9be" id="udeaaf9be">​</span><br></p>
  <ul list="u51cbf75e">
   <li fid="ub6d1e14a" data-lake-id="udbcd0254" id="udbcd0254"><span data-lake-id="ua78eca96" id="ua78eca96">对象终结规则（Finalizer Rule）： 一个对象的初始化完成（构造函数执行结束）happens-before 它的 finalize() 方法的开始。即在对象被回收前，其构造过程已经完全结束。</span></li>
  </ul>
  <p data-lake-id="u24a1859a" id="u24a1859a"><span data-lake-id="uacd76908" id="uacd76908">​</span><br></p>
  <pre lang="java"><code>
public class FinalizerExample {
    private int value;

    public FinalizerExample() {
        value = 10;  // 构造函数中的操作
    }

    @Override
    protected void finalize() {
        // 此方法 `happens-before` 构造函数中的操作
        if (value != 10) {
            // 这里不应该发生
        }
    }
}

</code></pre>
  <p data-lake-id="u6249bedb" id="u6249bedb"><span data-lake-id="ud38645d4" id="ud38645d4">​</span><br></p>
  <ul list="ub7f83438">
   <li fid="u7835b6c4" data-lake-id="u578174bd" id="u578174bd"><span data-lake-id="ud199754d" id="ud199754d">传递性（Transitivity）：如果操作 A 先行发生于操作 B，操作 B 先行发生于操作 C，那就可以得出操作 A 先行发生于操作 C 的结论。</span></li>
  </ul>
  <p data-lake-id="udf96ed8f" id="udf96ed8f"><span data-lake-id="u097879bc" id="u097879bc">​</span><br></p>
  <p data-lake-id="ua050d593" id="ua050d593"><span data-lake-id="ucc3a0405" id="ucc3a0405">​</span><br></p>
  <pre lang="java"><code>
public class TransitivityExample {
    private volatile boolean ready;
    private int number;

    public void writer() {
        number = 42;       // 操作 A
        ready = true;      // 操作 B
    }

    public void reader() {
        if (ready) {       // 操作 C（由于 ready 是 volatile，它 `happens-before` 这里的操作）
            assert number == 42;  // 因为 A `happens-before` B，B `happens-before` C，所以 A `happens-before` C
        }
    }
}

</code></pre>
  <p data-lake-id="u38aaeb74" id="u38aaeb74"><br></p>
  <p data-lake-id="u1163ba3f" id="u1163ba3f"><span data-lake-id="ue727fe70" id="ue727fe70">由于 ready 是一个 volatile 变量，写入 ready（操作 B）发生在读取 ready 之前，同样，写入 number（操作 A）发生在写入 ready 之前。根据传递性规则，写入 number 发生在读取 ready 之前。</span></p>
  <p data-lake-id="uab8568ae" id="uab8568ae"><br></p>
  <p data-lake-id="u4cd35a07" id="u4cd35a07"><br></p>
 </body>
</html>